/*
 * Copyright (c) 2014 pkzd/1.0, 颜文泽(robin)
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *     1 Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     2 Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include	<tty.h>
#include	<prf.h>
#include	<asm.h>
#include	<proc.h>
#include	<errno.h>
#include	<param.h>
#include	<string.h>

struct tty	cns[NCNS];

static ushort	*term = (ushort *)VGA_ADDR;
static uchar    normalmap[] = {
        [0x0b] = '0', [0x02] = '1', [0x03] = '2', [0x04] = '3', [0x05] = '4', [0x06] = '5',
        [0x07] = '6', [0x08] = '7', [0x09] = '8', [0x0a] = '9', [0x1e] = 'a', [0x30] = 'b', 
        [0x2e] = 'c', [0x20] = 'd', [0x12] = 'e', [0x21] = 'f', [0x22] = 'g', [0x23] = 'h',
        [0x17] = 'i', [0x24] = 'j', [0x25] = 'k', [0x26] = 'l', [0x32] = 'm', [0x31] = 'n',
        [0x18] = 'o', [0x19] = 'p', [0x10] = 'q', [0x13] = 'r', [0x1f] = 's', [0x14] = 't',
        [0x16] = 'u', [0x2f] = 'v', [0x11] = 'w', [0x2d] = 'x', [0x15] = 'y', [0x2c] = 'z',
        [0x29] = '`', [0x0c] = '-', [0x0d] = '=', [0x1a] = '[', [0x1b] = ']', [0x27] = ';',
        [0x28] = '\'', [0x33] = ',', [0x34] = '.', [0x35] = '/', [0x2b] = '\\', [0x39] = ' ',
        [0x1c] = Ctrl('J'), [0x0e] = Ctrl('H'), [0x0f] = Ctrl('I'),
};

static uchar shiftmap[] = {
        [0x0b] = ')', [0x02] = '!', [0x03] = '@', [0x04] = '#', [0x05] = '$', [0x06] = '%',
        [0x07] = '^', [0x08] = '&', [0x09] = '*', [0x0a] = '(', [0x1e] = 'A', [0x30] = 'B', 
        [0x2e] = 'C', [0x20] = 'D', [0x12] = 'E', [0x21] = 'F', [0x22] = 'G', [0x23] = 'H',
        [0x17] = 'I', [0x24] = 'J', [0x25] = 'K', [0x26] = 'L', [0x32] = 'M', [0x31] = 'N',
        [0x18] = 'O', [0x19] = 'P', [0x10] = 'Q', [0x13] = 'R', [0x1f] = 'A', [0x14] = 'T',
        [0x16] = 'U', [0x2f] = 'V', [0x11] = 'W', [0x2d] = 'X', [0x15] = 'Y', [0x2c] = 'Z',
        [0x29] = '~', [0x0c] = '_', [0x0d] = '+', [0x1a] = '{', [0x1b] = '}', [0x27] = ':',
        [0x28] = '"', [0x33] = '<', [0x34] = '>', [0x35] = '?', [0x2b] = '|', [0x39] = ' ',
        [0x1c] = Ctrl('J'), [0x0e] = Ctrl('H'), [0x0f] = Ctrl('I'), 
};

static uchar ctrlmap[] = {
	[0x24] = Ctrl('J'), [0x23] = Ctrl('H'), [0x17] = Ctrl('I'), [0x32] = Ctrl('M'),
	[0x20] = Ctrl('D'), [0x16] = Ctrl('U'), [0x2e] = Ctrl('C'), [0x10] = Ctrl('Q'),
	[0x1f] = Ctrl('S'), [0x2b] = Ctrl('\\'), [0x26] = Ctrl('L'),
};

static uint 	getpos(void);
static void	setpos(uint pos);
static void 	setstart(uint pos);

static uint
getpos(void)
{
        uint pos;

	outb(VGA_CUR_H, VGA_CMD);
	pos = inb(VGA_DAT);
	outb(VGA_CUR_L, VGA_CMD);
	pos = (pos << 8) | inb(VGA_DAT);
	return pos;
}

static void
setpos(uint pos)
{
	outb(VGA_CUR_H, VGA_CMD);
	outb((uchar)(pos >> 8), VGA_DAT);
	outb(VGA_CUR_L, VGA_CMD);
	outb((uchar)pos, VGA_DAT);
	term[pos] = ' ' | VGA_BLACK_WHITE;
}

static void
setstart(uint pos)
{
	outb(VGA_MEM_H, VGA_CMD);
	outb((uchar)(pos >> 8), VGA_DAT);
	outb(VGA_MEM_L, VGA_CMD);
	outb((uchar)pos, VGA_DAT);
}

void 
cnsclear(struct tty *tp)
{
	uint pos;

	pos = getpos();
	while(pos % VGA_PER_CNT)
		term[pos--] = ' ' | VGA_BLACK_WHITE;
	setpos(pos);
}

void
cnsint(void)
{
	int c, mode;
	struct tty *tp;

	if(inb(KLSRT) & KLDIB){
		tp = up->p_ttyp;
		mode = tp->t_mode;
		switch(c = inb(KLDAT)){
		case 0xe0:
		case 0xe1:
			break;
		case 0x3a:
			mode ^= CAPLOCK;
			break;
		case 0x2a:
		case 0x36:
			mode |= SHIFT;
			break;
		case 0x1d:
			mode |= CTRL;
			break;
		case 0x38:
			mode |= ALT;
			break;
		case 0xaa:
		case 0xb6:
			mode &= ~(SHIFT);
			break;
		case 0x9d:
			mode &= ~(CTRL);
			break;
		case 0xb8:
			mode &= ~(ALT);
			break;
		case 0x3b:
                case 0x3c:
                case 0x3d:
                case 0x3e:
                case 0x3f:
                case 0x40:
                case 0x41:
#if 0
                        if(mode & CTRL){
				tp->t_pos = getpos();
                                cnsopen(MAKEDEV(0, c - 0x3b), 0);
				return;	
                        }
#endif
                        break;
		default:
			if(c & 0x80)
				break;
			if(mode & SHIFT){
				c = shiftmap[c];
			}else if(mode & CTRL){
				c = ctrlmap[c];
			}else
				c = normalmap[c];
			if(mode & CAPLOCK){
				if(c >= 'a' && c <= 'z')
					c += 'A' - 'a';
				else if(c >= 'A' && c <= 'Z')
					c += 'a' - 'A';
			}
			ttyinput(c, tp);
			break;
		}
		tp->t_mode = mode;
	}
}

int
cnsopen(dev_t dev, uint flag)
{
	uint d;
	struct tty *tp;

	if(MAJOR(dev) || (d = MINOR(dev)) >= NCNS){
		up->p_error = ENXIO;
		return -1;
	}
	tp = up->p_ttyp = &cns[d];
	tp->t_mode = 0;
	if((tp->t_state & ISOPEN) == 0){
		ttychars(tp);
		tp->t_dev = dev;
		tp->t_pos = d * VGA_PER_CNT;
		tp->t_state = ISOPEN | CARR_ON;
		tp->t_flags = XTABS | ECHO;
	}	
	setpos(tp->t_pos);
	setstart(tp->t_pos);
	return 0;
}

int
cnsclose(dev_t dev)
{
	int d;
	struct tty *tp;

	if(MAJOR(dev) || (d = MINOR(dev)) >= NCNS){
		up->p_error = ENXIO;
		return -1;
	}
	tp = &cns[d];
	flushtty(tp);
	cnsclear(tp);
	return (tp->t_state = 0);
}

int
cnsread(dev_t dev, void *buf, size_t cnt)
{
	char *p;
	size_t i;
	struct tty *tp;

	tp = up->p_ttyp; 
	if(!(tp->t_state & (ISOPEN | CARR_ON))){
                up->p_error = EBADF;
                return -1;
        }
	p = buf;
	for(i = 0; i < cnt; i++){
		if((p[i] = getchar()) == EOF)
			break;
	}
	return i;
}

int
cnswrite(dev_t dev, const void *buf, size_t cnt)
{
	size_t i;
	const char *p;
	struct tty *tp;

	tp = up->p_ttyp; 
	if(!(tp->t_state & (ISOPEN | CARR_ON))){
		up->p_error = EBADF;
		return -1;
	}
	p = buf;
	for(i = 0; i < cnt; i++)
		putchar(p[i]);
	return i;
}

int
getchar(void)
{
	struct tty *tp;

	tp = up->p_ttyp;
retry:
	if(tp->t_canq.c_cc)
		return getc(&tp->t_canq);
	if(!tp->t_delct)
		sleep(&tp->t_rawq, PTTIN);
	if(!tp->t_delct)
		goto retry;
	canon(tp);
	return getc(&tp->t_canq);
}

void
putchar(int c)
{
	dev_t d;
	uint i, pos;
	struct tty *tp;

	c &= 0xff;
	tp = up->p_ttyp;
	d = MINOR(tp->t_dev);
	if(!(tp->t_state & (ISOPEN | CARR_ON)))
		return;
	pos = getpos();
	switch(c){
	case '\n':
                pos += VGA_COL_SIZ - pos % VGA_COL_SIZ;
                break;
        case '\t':
                pos += VGA_TAB_SIZ - pos % VGA_TAB_SIZ;
                break;
	case '\b':
		if(pos % VGA_COL_SIZ)
			pos--;
		break;
	case 0x15:
		while(pos % VGA_COL_SIZ)
			term[pos--] = ' ' | VGA_BLACK_WHITE;
		break;
        default:
                term[pos++] = c | VGA_BLACK_WHITE;
                break;
	}
	if((pos % VGA_PER_CNT) == 0 && pos != (VGA_PER_CNT * d)){
                memmove((uchar *)(term + d * VGA_PER_CNT), (uchar *)(term + d * VGA_PER_CNT + VGA_COL_SIZ), (VGA_PER_CNT - VGA_COL_SIZ) * 2);
                pos -= VGA_COL_SIZ;
                memset((uchar*)(term + d * VGA_PER_CNT + pos), 32, VGA_COL_SIZ * 2);
		for(i = 0; i < VGA_COL_SIZ; i++)
			term[pos + i] = ' ' | VGA_BLACK_WHITE;
        }
	setpos(pos);
}
